local jass = require "jass.common"
local japi = require "jass.japi"
local code = require "jass.code"
local dbg = require "jass.debug"
local slk = require "jass.slk"

local EVENT_UNIT_DAMAGED = jass.EVENT_UNIT_DAMAGED
local EVENT_UNIT_TARGET_IN_RANGE = jass.EVENT_UNIT_TARGET_IN_RANGE

local damage = {}
setmetatable(damage, damage)
ac.damage = damage

-- [攻击类型表]
damage.AmorTypes = ac.unit.AmorTypes

-- [护甲类型表]
damage.AttackTypes = ac.unit.AttackTypes

-- [护甲因子]
damage.DefenseArmor = tonumber(slk.misc.Misc.DefenseArmor) or 0.06

-- [负护甲增伤上限]
damage.DefenseArmorMax = 0.70

-- [攻击护甲类型克制关系表]
damage.DamageBonus = {}

for i, name in ipairs({
    "DamageBonusSpells", --法术
    "DamageBonusNormal", --普通
    "DamageBonusPierce", --穿刺
    "DamageBonusSiege",  --攻城
    "DamageBonusMagic",  --魔法
    "DamageBonusChaos",  --混乱
    "DamageBonusHero",   --英雄
}) do
    damage.DamageBonus[i - 1] = {}
    local data = slk.misc.Misc[name]
    if not data then
        log.error "读取slk数据失败"
    end
    local info = string.split(data, ",")
    for o, value in ipairs(info) do
        damage.DamageBonus[i - 1][o - 1] = tonumber(value)
    end
end

-- [武器音效]
damage.weapons = {
    ["没有"] = jass.ConvertWeaponType(0),
    ["金属轻砍"] = jass.ConvertWeaponType(1),
    ["金属中砍"] = jass.ConvertWeaponType(2),
    ["金属重砍"] = jass.ConvertWeaponType(3),
    ["金属轻切"] = jass.ConvertWeaponType(4),
    ["金属中切"] = jass.ConvertWeaponType(5),
    ["金属重切"] = jass.ConvertWeaponType(6),
    ["金属中击"] = jass.ConvertWeaponType(7),
    ["金属重击"] = jass.ConvertWeaponType(8),
    ["金属中刺"] = jass.ConvertWeaponType(9),
    ["金属重刺"] = jass.ConvertWeaponType(10),
    ["木头轻切"] = jass.ConvertWeaponType(11),
    ["木头中切"] = jass.ConvertWeaponType(12),
    ["木头重切"] = jass.ConvertWeaponType(13),
    ["木头轻击"] = jass.ConvertWeaponType(14),
    ["木头中击"] = jass.ConvertWeaponType(15),
    ["木头重击"] = jass.ConvertWeaponType(16),
    ["木头轻刺"] = jass.ConvertWeaponType(17),
    ["木头中刺"] = jass.ConvertWeaponType(18),
    ["斧头轻切"] = jass.ConvertWeaponType(19),
    ["斧头中切"] = jass.ConvertWeaponType(20),
    ["斧头重切"] = jass.ConvertWeaponType(21),
    ["斧头中击"] = jass.ConvertWeaponType(22),
    ["岩石重击"] = jass.ConvertWeaponType(23),
}

-- 获取单位的护甲减伤
function damage.get_amor_defens(unit)
    local amor = unit:get('护甲')
    if amor >= 0 then
        return (damage.DefenseArmor * amor / (damage.DefenseArmor * amor + 1)) * 100
    else
        return -math.min(1 - (1 - damage.DefenseArmor) ^ (-1 * amor), damage.DefenseArmorMax) * 100
    end
end

function damage.action()
    local event = jass.GetTriggerEventId()
    if event == EVENT_UNIT_DAMAGED then
        local damage_type = japi.EXGetEventDamageData(1)
        japi.EXSetEventDamage(0.0)
        if damage_type ~= 0 then
            local source = ac.unit(jass.GetEventDamageSource())
            local target = ac.unit(jass.GetTriggerUnit())
            if source and target then
                if source.attacker then
                    source.attacker:notify("damage", target)
                    return
                end
                --local name = source:get_attack_type()
                if source:dispatch('单位-攻击出手',source,target) then
                    return
                end
                if target:dispatch('单位-受到攻击出手',target,source) then
                    return
                end
                target:damage {
                    damage = source:get('攻击'),
                    common_attack = true,
                    attack = true,
                    source = source,
                }
            end
        end
    -- elseif event == EVENT_UNIT_TARGET_IN_RANGE then
    --     local source = ac.unit(jass.GetTriggerUnit())
    --     local target = ac.unit(jass.GetEventTargetUnit())
    --     if source and target then
    --         source:notify("单位-获取攻击目标", source, target)
    --     end
    end
end

ac.game:on "单位-初始化" (function(_, unit)
    local handle = unit.handle
    if damage.trigger then
        damage.count = damage.count + 1
        jass.TriggerRegisterUnitEvent(damage.trigger, handle, EVENT_UNIT_DAMAGED)
        --jass.TriggerRegisterUnitEvent(damage.trigger, handle, EVENT_UNIT_TARGET_IN_RANGE)
    end
end)

function damage.update()
    if damage.count > 10000 then
        local trigger = damage.trigger
        code.TriggerClearActions2(trigger)
        jass.DestroyTrigger(trigger)
        dbg.handle_unref(trigger)
        
        trigger = jass.CreateTrigger()
        damage.trigger = trigger
        dbg.handle_ref(trigger)
        jass.TriggerAddAction(trigger, damage.action)
        damage.count = 0
        for handle in pairs(ac.unit.allunits) do
            jass.TriggerRegisterUnitEvent(trigger, handle, EVENT_UNIT_DAMAGED)
            -- jass.TriggerRegisterUnitEvent(trigger, handle, EVENT_UNIT_TARGET_IN_RANGE)
        end
    end
end

function damage.init()
    local trigger = jass.CreateTrigger()
    damage.trigger = trigger
    dbg.handle_ref(trigger)
    jass.TriggerAddAction(trigger, damage.action)
    damage.count = 0
    ac.loop(2000, function()
        damage.update()
    end)
end

local mt = {} -- 结构

damage.__index = mt

--对象类型
mt.type = "damage"

--伤害来源
mt.source = nil

--伤害目标
mt.target = nil

--伤害关联技能
mt.skill = nil

--伤害值
mt.damage = 0

--计算后的伤害
mt.current_damage = 0

--原因
mt.reason = "未知"

--是否是普通攻击
mt.attack = false

--是否触发仇恨
mt.enmity = true

--武器类型
mt.weapon = "没有"

--伤害类型
mt.damage_type = "普通"

--是否是Aoe伤害
mt.aoe = false

--是持续伤害
mt.always = false

--是否是暴击
mt.crit_flag = false

--暴击倍率
mt.crit_percent = 0

--暴击追加
mt.crit_growth = 0

--命中几率
mt.exact_percent = 100

--是否被闪避
mt.miss_flag = false

--是否是有效伤害
mt.success = true

-- 乘法叠加倍率
mt.change_mul = 1

-- 加法叠加倍率
mt.change_add = 1

--设置当前伤害
function mt:set_current_damage(dmg)
    self.current_damage = dmg
    return dmg
end

--获取当前伤害
function mt:get_current_damage()
    return self.current_damage or self.damage
end

--判断伤害类型
function mt:is_type(name)
    return self.damage_type == name
end

--获取伤害类型
function mt:get_damage_type()
    return self.damage_type
end

--是持续伤害
function mt:is_always()
    return self.always
end

--判断伤害原因
function mt:of_reason(name)
    return self.reason:find(name) ~= nil
end

--获取伤害位置
function mt:get_point()
    if not self.point then
        self.point = self.source:get_point()
    end
    return self.point
end

--获取原始伤害
function mt:get_base_damage()
    return self.damage
end

-- 伤害加深乘法叠加
function mt:mul(n)
	if n == 0 then return end
	self.change_mul = self.change_mul * (1 + n / 100)
end

-- 伤害减免除法叠加
function mt:div(n)
	self.change_mul = self.change_mul * (1 - n / 100)
end

-- 伤害调整
function mt:on_manipulation()
	self.current_damage = self.current_damage * self.change_mul
end

--是攻击
function mt:is_attack()
    return self.attack
end

--是普通攻击
function mt:is_common_attack()
    return self.common_attack
end

--是范围伤害
function mt:is_aoe()
    return self.aoe
end

--是技能伤害
function mt:is_skill()
    return self.skill ~= nil
end

--是否是暴击
function mt:is_crit()
    return self.crit_flag
end

--是否被闪避
function mt:is_miss()
    return self.miss_flag
end

--计算暴击
function mt:on_crit(r)
    local source = self.source 
    local target = self.target 
    source:notify("单位-计算暴击", source, target, self)
    return self.crit_flag
end

--计算闪避
function mt:on_miss()
    local source = self.source 
    local target = self.target 
    if not target:has_restriction "闪避禁用" then 
        target:notify("单位-计算闪避", target, source, self)
        if not target._dodge_prd then
            target._dodge_prd = ac.pseudo_random()
        end
        if target._dodge_prd:random(100 - self.exact_percent) then 
            self.miss_flag = true 
        end 
    end 
    return self.miss_flag
end

-- 伤害计算流程
function damage.damageCommon(self)
    if self:is_type("真实") then
        return true
    end

    local source = self.source
    local target = self.target

    return true
end

-- 伤害结算后触发事件
function damage.damageAttach(self)
    if self:is_type("真实") then
        return
    end

    local source = self.source
    local target = self.target

    -- local steal = source:get "吸血" * 0.01

    -- if self:is_common_attack() and steal > 0 and not target:is_build() and not target:is_ward() then
    --     source:heal {
    --         heal = self:get_current_damage() * steal,
    --         source = self.target,
    --         reason = "吸血",
    --     }
    -- end
end

-- 击杀检测
function mt:on_kill()
	local target = self.target
	
	if self.nonlethal_damage then
		target:set('生命', 1000)
		target:set('生命', 1)
		return false
	end
    
	if target:has_restriction '免死' then
		target:set('生命', target:get('生命上限')*0.01)
		return false
    end
    
	if target:dispatch('单位-拦截死亡', self) then
		local rate = self.life_rate or 1
		target:set('生命', target:get('生命上限')*rate/100)
		return false
    end
	return target:kill(self.source, self)
end

-- 扣除生命
function damage:on_cost_life()
    local target = self.target

	local life = target:get '生命'
	if life <= self.current_damage then
		self.real_damage = life
		self:on_kill()
	else
		self.real_damage = self.current_damage
		target:set('生命', life - self.current_damage)
	end
end

-- 伤害事件流程
function damage.call(self)
    local source = self.source
    local target = self.target

    if not target:is_alive() or target:has_restriction "免伤" then
        return false
    end

    if not source then
        self.source = target
        source = target
    end

    self.current_damage = self:get_base_damage()

    -- 检验伤害有效性
    if source:dispatch('单位-拦截造成伤害', self) then
        return self
    end

    if target:dispatch('单位-拦截受到伤害', self) then
        return self
    end
    
    
    -- 攻击命中在伤害有效性后结算
	if not self.hp_remove then
        source:notify('单位-造成伤害前', source, target ,self)
        target:notify('单位-受到伤害前', target, source ,self)

        if self:is_common_attack() then
            source:notify('单位-攻击命中', source, target, self)
            target:notify('单位-受到攻击命中', target, source ,self)
        end
    end

    if damage.damageCommon(self) == false then
        return false
    end

	if not self.hp_remove then
		source:notify('单位-即将造成伤害', source, target, self)
		target:notify('单位-即将受到伤害', target, source, self)
	end

	-- 加法与乘法的伤害叠加
	self:on_manipulation()

    if self.enmity and (not target:current_order() == "attack") then 
        jass.UnitDamageTarget(source.handle, target.handle, 1, true, true, 1, 4, damage.weapons[self.weapon] or 0)
    else
        jass.UnitDamageTarget(source.handle, target.handle, 0, true, true, 1, 4, damage.weapons[self.weapon] or 0)
    end


    target:dispatch("单位-伤害护盾计算", target, source, self)

    self:set_current_damage(math.floor(math.max(self.current_damage + 0.5, 0)))

	-- 造成伤害
	self.current_damage = math.floor(self.current_damage + 0.5)
	if self.current_damage <= 0 then
        self.current_damage = 0
        return self
	end
    
    self:on_cost_life()

    if not self.hp_remove then
        source:notify("单位-造成伤害", source, target, self)
        target:notify("单位-受到伤害", target, source, self)

        -- 计算吸血等等
        damage.damageAttach(self)
    end

    source:notify("单位-造成伤害结束", source, target, self)
    target:notify("单位-受到伤害结束", target, source, self)
    source:notify('单位-伤害显示',source,self)
    return true
end

-- 对单位造成伤害
function ac.unit.__index:damage(object)
    local object = object or {}
    object.target = self
    object.damage_type = name
    setmetatable(object, damage)
    damage.call(object)
    return object
end

--创建一次伤害
function ac.unit.__index:create_damage(target,damage_value,damage_type)
    local object = {}
    object.source = self
    object.target = target
    object.skill = skill
    object.damage = damage_value
    if damage_type=='普攻' then
        object.common_attack = true
        object.attack = true
    elseif type(damage_type)=='string' then
        object.damage_type = damage_type
    elseif damage_type then
        object.skill = damage_type
        object.damage_type = damage_type.damage_type
    end
    setmetatable(object, damage)
    damage.call(object)
    return object
end

--[[
造成一次AOE伤害
params(点,范围)
]]
function ac.unit.__index:aoe_damage(target,area,damage,damage_type,func1)
    local lock = {}
    local list = {}
    if type(func1)=='table' then
        lock = func1
        func1 = nil
    end
    for u in ac.selector('unit')
        : range(target,area)
        : enemy(self)
        : loop()
    do
        if func1==nil or func1(u)~=true then
            if lock[u.handle]==nil then
                lock[u.handle] = true
                self:create_damage(u,damage,damage_type)
                list[#list+1] = u
            end
        end
    end
    return list
end

--[[
造成一次AOE伤害
params(点,范围)
]]
function ac.unit.__index:line_damage(target,angle,len,width,damage,skill,func1)
    for u in ac.selector('unit')
        : line(target,angle,len,width)
        : enemy(self)
        : loop()
    do
        if func1 then
            func1(u)
        end
        self:create_damage(u,damage,skill)
    end
end


--[[
创建一个从一个点往外扩散伤害
params(点,范围,速度,伤害函数)
]]
function ac.unit.__index:diffuse_damage(target,area,speed)
    local speed = speed or area
    return function(object)
        local real_area = 0
        local group = {}
        local start = object.start
        local finish = object.finish
        local point = target:get_point()
        object.source = object.source or self
        local timer = ac.loop(30,function(t)
            local min_area = real_area
            real_area = real_area + speed*0.03
            if real_area>=area then
                t:remove()
            end
            for u in ac.selector('unit')
                : range(point,real_area)
                : enemy(self)
                : loop()
            do
                local p = u:get_point()
                if group[u.handle]==nil and p*point>=min_area then
                    group[u.handle] = true
                    if start==nil or start(u)==nil then
                        local object = table.copy(object)
                        u:damage(object)
                        if finish then
                            finish(u)
                        end
                    end
                end
            end
        end)

        return timer
    end
end


return damage
